package com.seadragon.app.ksr.util.tiger;

import static com.seadragon.app.ksr.util.BinUtils.getDouble;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.seadragon.app.ksr.model.SHPFileHeader;
import com.seadragon.app.ksr.model.SHPFileRecord;
import com.seadragon.app.ksr.model.SHPFileRecordHeader;
import com.seadragon.app.ksr.model.shape.PolyLineShape;
import com.seadragon.app.ksr.model.shape.PolygonShape;
import com.seadragon.app.ksr.model.shape.ShapeType;
import com.vividsolutions.jts.geom.Coordinate;

public class SHPFileReader extends BufferedInputStream {
	private static final Logger logger = LoggerFactory.getLogger(SHPFileReader.class);
	private static final int FILE_CODE_OFFSET = 0;
	private static final int FILE_LENGTH_OFFSET = 24;
	private static final int VERSION_OFFSET = 28;
	private static final int SHAPE_TYPE_OFFSET = 32;
	private static final int BOUNDING_BOX_XMIN_OFFSET = 36;
	private static final int BOUNDING_BOX_YMIN_OFFSET = 44;
	private static final int BOUNDING_BOX_XMAX_OFFSET = 52;
	private static final int BOUNDING_BOX_YMAX_OFFSET = 60;
	private static final int BOUNDING_BOX_ZMIN_OFFSET = 68;
	private static final int BOUNDING_BOX_ZMAX_OFFSET = 76;
	private static final int BOUNDING_BOX_MMIN_OFFSET = 84;
	private static final int BOUNDING_BOX_MMAX_OFFSET = 92;

	private static final int FILE_HEADER_LENGTH = 100;
	private static final int RECORD_HEADER_LENGTH = 8;
	private static final int RECORD_NUMBER_OFFSET = 0;
	private static final int RECORD_CONTENT_LENGTH = 4;
	private static final int RECORD_SHAPE_TYPE_OFFSET = 0;
	private static final int RECORD_BOUNDING_BOX_XMIN_OFFSET = 4;
	private static final int RECORD_BOUNDING_BOX_YMIN_OFFSET = 12;
	private static final int RECORD_BOUNDING_BOX_XMAX_OFFSET = 20;
	private static final int RECORD_BOUNDING_BOX_YMAX_OFFSET = 28;
	private static final int RECORD_NUM_PARTS_OFFSET = 36;
	private static final int RECORD_NUM_POINTS_OFFSET = 40;
	private static final int RECORD_PARTS_OFFSET = 44;

	private SHPFileHeader shpFileHeader;
	private int pos = 0;

	public SHPFileReader(File file) throws FileNotFoundException, IOException {
		super(new FileInputStream(file));
		shpFileHeader = new SHPFileHeader();
		byte[] bbuf = new byte[FILE_HEADER_LENGTH];
		read(bbuf, 0, FILE_HEADER_LENGTH);

		shpFileHeader.setFileCode(getInt(bbuf, FILE_CODE_OFFSET, false));
		shpFileHeader.setFileLength(2*getInt(bbuf, FILE_LENGTH_OFFSET, false));
		shpFileHeader.setVersion(getInt(bbuf, VERSION_OFFSET, true));
		shpFileHeader.setShapeType(ShapeType.getInstance(getInt(bbuf, SHAPE_TYPE_OFFSET, true)));
		shpFileHeader.setBoundingBoxXMin(getDouble(bbuf, BOUNDING_BOX_XMIN_OFFSET, true));
		shpFileHeader.setBoundingBoxYMin(getDouble(bbuf, BOUNDING_BOX_YMIN_OFFSET, true));
		shpFileHeader.setBoundingBoxXMax(getDouble(bbuf, BOUNDING_BOX_XMAX_OFFSET,true));
		shpFileHeader.setBoundingBoxYMax(getDouble(bbuf, BOUNDING_BOX_YMAX_OFFSET,true));
		shpFileHeader.setBoundingBoxZMin(getDouble(bbuf, BOUNDING_BOX_ZMIN_OFFSET,true));
		shpFileHeader.setBoundingBoxZMax(getDouble(bbuf, BOUNDING_BOX_ZMAX_OFFSET,true));
		shpFileHeader.setBoundingBoxMMin(getDouble(bbuf, BOUNDING_BOX_MMIN_OFFSET,true));
		shpFileHeader.setBoundingBoxMMax(getDouble(bbuf, BOUNDING_BOX_MMAX_OFFSET,true));
		pos += FILE_HEADER_LENGTH;
	}

	public boolean hasNext() {
		if(pos < shpFileHeader.getFileLength())
			return true;
		return false;
	}

	public SHPFileRecord next() throws IOException {
		SHPFileRecord record = new SHPFileRecord();
		// process record header
		byte[] bbuf = new byte[RECORD_HEADER_LENGTH];
		read(bbuf, 0, RECORD_HEADER_LENGTH);
		pos += RECORD_HEADER_LENGTH;
		SHPFileRecordHeader shpFileRecordHeader = new SHPFileRecordHeader();
		shpFileRecordHeader.setRecordNumber(getInt(bbuf, RECORD_NUMBER_OFFSET, false));
		shpFileRecordHeader.setContentLength(2*getInt(bbuf, RECORD_CONTENT_LENGTH, false));
		record.setRecordHeader(shpFileRecordHeader);
		
		// process record content
		bbuf = new byte[shpFileRecordHeader.getContentLength()];
		read(bbuf, 0, shpFileRecordHeader.getContentLength());
		pos += shpFileRecordHeader.getContentLength();
		if (shpFileHeader.getShapeType() == ShapeType.POLYGON) {
			PolygonShape content = new PolygonShape();
			record.setRecordContent(content);

			if(content.getShapeType() != ShapeType.getInstance(getInt(bbuf, RECORD_SHAPE_TYPE_OFFSET, true))){
				logger.error("File format is wrong!");
			}
			content.setXmin(getDouble(bbuf, RECORD_BOUNDING_BOX_XMIN_OFFSET,true));
			content.setYmin(getDouble(bbuf, RECORD_BOUNDING_BOX_YMIN_OFFSET,true));
			content.setXmax(getDouble(bbuf, RECORD_BOUNDING_BOX_XMAX_OFFSET,true));
			content.setYmax(getDouble(bbuf, RECORD_BOUNDING_BOX_YMAX_OFFSET,true));
			
			content.setNumOfParts(getInt(bbuf, RECORD_NUM_PARTS_OFFSET, true));
			
			content.setNumOfPoints(getInt(bbuf, RECORD_NUM_POINTS_OFFSET, true));
			
			int[] parts = new int[content.getNumOfParts()];
			int offset = RECORD_PARTS_OFFSET;
			for(int i = 0; i < content.getNumOfParts(); i++){
				parts[i] = getInt(bbuf, offset, true);
				offset += 4;
			}
			content.setBlob(Arrays.copyOfRange(bbuf, offset, content.getNumOfPoints()*16));
			Coordinate[] points = new Coordinate[content.getNumOfPoints()];
			for(int i = 0; i < content.getNumOfPoints(); i++){
				double lng = getDouble(bbuf, offset,true);
				offset += 8;
				double lat = getDouble(bbuf, offset,true);
				points[i] = new Coordinate(lng, lat);
				offset += 8;
			}
			content.setParts(parts);
			content.setPoints(points);
		} else if (shpFileHeader.getShapeType() == ShapeType.MULTIPATCH) {

		} else if (shpFileHeader.getShapeType() == ShapeType.MULTIPOINT) {

		} else if (shpFileHeader.getShapeType() == ShapeType.MULTIPOINTM) {

		} else if (shpFileHeader.getShapeType() == ShapeType.MULTIPOINTZ) {

		} else if (shpFileHeader.getShapeType() == ShapeType.NULL_SHAPE) {

		} else if (shpFileHeader.getShapeType() == ShapeType.POINTM) {

		} else if (shpFileHeader.getShapeType() == ShapeType.POINTZ) {

		} else if (shpFileHeader.getShapeType() == ShapeType.POLYGON) {

		} else if (shpFileHeader.getShapeType() == ShapeType.POLYGONM) {

		} else if (shpFileHeader.getShapeType() == ShapeType.POLYGONZ) {

		} else if (shpFileHeader.getShapeType() == ShapeType.POLYLINE) {
			PolyLineShape content = new PolyLineShape();
			record.setRecordContent(content);

			if(content.getShapeType() != ShapeType.getInstance(getInt(bbuf, RECORD_SHAPE_TYPE_OFFSET, true))){
				logger.error("File format is wrong!");
			}
			content.setXmin(getDouble(bbuf, RECORD_BOUNDING_BOX_XMIN_OFFSET,true));
			content.setYmin(getDouble(bbuf, RECORD_BOUNDING_BOX_YMIN_OFFSET,true));
			content.setXmax(getDouble(bbuf, RECORD_BOUNDING_BOX_XMAX_OFFSET,true));
			content.setYmax(getDouble(bbuf, RECORD_BOUNDING_BOX_YMAX_OFFSET,true));
			
			content.setNumOfParts(getInt(bbuf, RECORD_NUM_PARTS_OFFSET, true));
			content.setNumOfPoints(getInt(bbuf, RECORD_NUM_POINTS_OFFSET, true));
			
			int[] parts = new int[content.getNumOfParts()];
			int offset = RECORD_PARTS_OFFSET;
			for(int i = 0; i < content.getNumOfParts(); i++){
				parts[i] = getInt(bbuf, offset, true);
				offset += 4;
			}
			content.setBlob(Arrays.copyOfRange(bbuf, offset, offset+content.getNumOfPoints()*16));
			Coordinate[] points = new Coordinate[content.getNumOfPoints()];
			for(int i = 0; i < content.getNumOfPoints(); i++){
				double lng = getDouble(bbuf, offset,true);
				offset += 8;
				double lat = getDouble(bbuf, offset,true);
				points[i] = new Coordinate(lng, lat);
				offset += 8;
			}
			
			content.setParts(parts);
			content.setPoints(points);

		} else if (shpFileHeader.getShapeType() == ShapeType.POLYLINEM) {

		} else if (shpFileHeader.getShapeType() == ShapeType.POLYLINEZ) {

		} else {

		}
		return record;
	}
	
	public int getInt(byte[] bbuf, int offset, boolean littleEndian){
		ByteBuffer ibuf = null;
		if(littleEndian){
			ibuf = ByteBuffer.allocate(4);
			for(int i = 0; i < 4; i++){
				ibuf.put(i, bbuf[offset+3-i]);
			}
		}else{
			ibuf = ByteBuffer.wrap(bbuf, offset, 4);
		}
		return ibuf.getInt();
	}
	
	public static void main(String[] argv) {
		SHPFileReader shpFileReader = null;
		try {
			
//			shpFileReader = new SHPFileReader(new File("data\\tl_2012_us_county\\tl_2012_us_county.shp"));
			shpFileReader = new SHPFileReader(new File("data\\tl_2012_24031_edges\\tl_2012_24031_edges.shp"));
			while(shpFileReader.hasNext()){
				SHPFileRecord record = shpFileReader.next();
				logger.info(record.toString() + ": " + ((PolyLineShape)record.getRecordContent()).getNumOfParts());
			}
			// logger.info("Total number of records are processed: " +
			// dbfFileReader.count );
		} catch (Exception ex) {
			// logger.info("Total number of records are processed: " +
			// dbfFileReader.count );
			logger.error(ex.getMessage(), ex);
		} finally {
			if (shpFileReader != null) {
				try {
					shpFileReader.close();
				} catch (IOException ex) {

				}
			}
		}
	}
}
